package com.zwh.se.design_pattern.creational.singleton;

import java.io.*;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;

/**
 * 静态内部类方式<p>
 * 如果说懒汉式和饿汉式都或多或少有些缺点的话，那么使用这种方式来实现的单例就是比较完美的一种。
 * <p>
 * 原理:
 * 根据 静态内部类 的特性(外部类的加载不影响内部类)，同时解决了按需加载、线程安全的问题，同时实现简洁。
 * <p>
 * 优点: 1、实现懒加载(静态内部类本身特性)   2、线程安全(类是由JVM加载的，且只加载一次，因此能保证只有1个单例)
 * <p>
 * 缺陷: 这不属于缺点，只能视作缺陷。1、单例可以被反射破坏 2、单例可以被序列化破坏。
 *
 * @author yexi
 */
public class StaticInnerClass implements Serializable {

    private StaticInnerClass() {
        //解决反射破坏单例的缺陷: 若判断出实例不为空则抛出异常
        if (StaticInnerClassHolder.INSTANCE != null) {
            throw new RuntimeException("不允许非法访问!");
        }
    }

    //内置私有的静态内部类。静态内部类在只加载外部类的情况下是不会初始化的。什么时候会初始化呢？
    //只有调用 getInstance() 的时候才初始化
    //那么线程安全是谁来保证的？是JVM来帮我们保证的。因为它加载一个类的时候，只会加载一次。
    //所以 StaticInnerClassHolder 只会加载一次，因此里面的 INSTANCE 也只会加载一次
    private static class StaticInnerClassHolder {
        //由于此时才初始化，所以实现了懒加载
        private final static StaticInnerClass INSTANCE = new StaticInnerClass();
    }

    //调用方法时，通过静态内部类来获取。此时才会初始化实例，避免了直接初始化的问题。
    public static StaticInnerClass getInstance() {
        return StaticInnerClassHolder.INSTANCE;
    }

    /**
     * 解决序列化破坏单例的缺陷: 提供一个readResolve方法。
     * 程序会判断是否有readResolve方法,如果存在则执行该方法,如果不存在则创建一个对象。
     * 原因在于 ObjectInputputStream 的 readObject 方法的调用栈上:
     *
     * @see ObjectInputStream#readObject0(Class, boolean)
     *        case TC_OBJECT
     *        return checkResolve(readOrdinaryObject(unshared));
     *
     * @see ObjectInputStream#readOrdinaryObject(boolean)
     *         //此处省略部分代码(只关注主要代码即可)
     *         ObjectStreamClass desc = readClassDesc(false);
     *         Object obj;
     *         try {
     *             //通过反射创建的这个obj对象，就是本方法要返回的对象，也可以暂时理解为是ObjectInputStream的readObject返回的对象。
     *             //isInstantiable：如果一个serializable的类可以在运行时被实例化，那么该方法就返回true
     *             //desc.newInstance：该方法通过反射的方式调用无参构造方法新建一个对象(注意是ObjectStreamClass类的newInstance方法而非当前类)。
     *             //                  ——这就是为什么序列化可以破坏单例
     *             obj = desc.isInstantiable() ? desc.newInstance() : null;
     *         } catch (Exception ex) {
     *             throw (IOException) new InvalidClassException(
     *                 desc.forClass().getName(),
     *                 "unable to create instance").initCause(ex);
     *         }
     *        if (obj != null &&
     *             handles.lookupException(passHandle) == null &&
     *             //如果实现了serializable 接口的类中包含readResolve则返回true
     *             desc.hasReadResolveMethod())
     *         {
     *             //通过反射的方式调用要被反序列化的类的readResolve方法。
     *             Object rep = desc.invokeReadResolve(obj);
     *         }
     *
     *
     *
     *
     */
//    private Object readResolve() {
//        return StaticInnerClassHolder.INSTANCE;
//    }

    public static void main(String[] args) throws Exception {

        List<Thread> threads = new ArrayList<>(100);
        for (int i = 0; i < 100; i++) {
            threads.add(new Thread(() -> System.out.println(StaticInnerClass.getInstance().hashCode())));
        }
        threads.forEach(Thread::start);
        Thread.sleep(1000);

        //1、反射对于单例的破坏
        try {
            Class<StaticInnerClass> clazz = StaticInnerClass.class;
            Constructor c = clazz.getDeclaredConstructor(null);
            //设置为true,就可以对类中的私有成员进行操作了
            c.setAccessible(true);
            Object instance1 = c.newInstance();
            Object instance2 = c.newInstance();
            System.out.println(instance1 == instance2);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //2、序列化对于单例的破坏
        //序列化对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile.obj"));
        oos.writeObject(StaticInnerClass.getInstance());
        //序列化对象输入流
        File file = new File("tempFile.obj");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        StaticInnerClass singleton = (StaticInnerClass) ois.readObject();
        //地址不一样，这说明: 对Singleton的序列化与反序列化得到的对象是一个新的对象，这就"破坏了Singleton的单例性"。
        System.out.println(singleton);
        System.out.println(StaticInnerClass.getInstance());
        //判断是否是同一个对象
        System.out.println(StaticInnerClass.getInstance() == singleton);//false

    }

}
